/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.sunrise.utility;

import com.github.dvdme.ForecastIOLib.FIODataPoint;
import com.sunrise.boundary.CalendarManager;
import com.sunrise.boundary.EventManager;
import com.sunrise.boundary.UserManager;
import com.sunrise.boundary.WeatherAlertManager;
import com.sunrise.boundary.WeatherManager;
import com.sunrise.entity.Calendar;
import com.sunrise.entity.Event;
import com.sunrise.entity.Location;
import com.sunrise.entity.User;
import com.sunrise.entity.WeatherAlert;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.junit.Before;

import javax.ejb.EJB;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.UserTransaction;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.After;
import org.junit.Test;
import static org.junit.Assert.*;
import org.junit.runner.RunWith;
import static org.mockito.Mockito.*;

/**
 *
 * @author frank
 */
@RunWith(Arquillian.class)
public class WeatherCheckerIT {

    @PersistenceContext
    EntityManager em;
    @Inject
    UserTransaction utx;
    @EJB
    WeatherAlertManager weatherAlertManager;
    @EJB
    EventManager eventManager;
    @EJB
    CalendarManager calendarManager;
    @EJB
    UserManager userManager;

    private User owner;
    private Calendar calendar;
    private final DateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss");

    @Deployment
    public static WebArchive createArchiveAndDeploy() {
        Class[] classes = {
            EventManager.class,
            WeatherManager.class,
            WeatherAlertManager.class,
            CalendarManager.class,
            UserManager.class,
            Event.class,
            User.class,
            Calendar.class,
            Location.class,
            WeatherChecker.class
        };

        WebArchive arch = ShrinkWrap.create(WebArchive.class);
        for (Class c : classes) {
            arch.addClass(c);
        }

        return arch.addPackage(WeatherAlert.class.getPackage())
                .addPackage(EventManager.class.getPackage())
                .addPackage(WeatherChecker.class.getPackage())
                .addAsResource("test-persistence.xml", "META-INF/persistence.xml")
                .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml");
    }

    @EJB
    WeatherChecker cut;

    @Before
    public void setUp() throws Exception {
        clearData();
        insertData();
        startTransaction();
    }

    @Test
    public void testWeahterAlertManagerInjected() throws Exception {
        assertNotNull(weatherAlertManager);
    }

    @Test
    public void testEventManagerInjected() throws Exception {
        assertNotNull(eventManager);
    }

    @Test
    public void testUserManagerInjected() throws Exception {
        assertNotNull(userManager);
    }

    @Test
    public void testEventsCorrectlySavedAndRetrieved() throws Exception {
        Date startDate = daysFromNow(10);
        Date endDate = add(startDate, java.util.Calendar.HOUR, 1);

        Event e = createOutdoorEvent(startDate, endDate);
        eventManager.save(e);

        List<Event> events = eventManager.findAllEvents();
        assertEquals(e, events.get(0));
    }

    @Test
    public void testWeatherCheckerChecksCorrectEvents() throws Exception {
        // create events that should be checked
        Event e1 = createOutdoorEvent(daysFromNow(1), daysFromNow(1));
        e1.setCheckedOneDay(false);
        e1.setCheckedThreeDays(true);
        eventManager.save(e1);

        Event e2 = createOutdoorEvent(secondsFromNow(2*60*60), daysFromNow(3*60*60));
        e2.setCheckedOneDay(false);
        e2.setCheckedThreeDays(true);
        eventManager.save(e2);

        // create events that should not be checked
        Event e3 = createOutdoorEvent(daysFromNow(4), daysFromNow(4));
        eventManager.save(e3);

        cut.checkWeather();
        
        e1 = eventManager.findEventByIdentifier(e1.getIdentifier());
        e2 = eventManager.findEventByIdentifier(e2.getIdentifier());
        e3 = eventManager.findEventByIdentifier(e3.getIdentifier());
        
        Event[] shouldBeCheckedOneDay = {e1, e2};
        Event[] sholdNotBeChecked = {e3};
        
        for (Event e : sholdNotBeChecked) {
            assertFalse(e.isCheckedOneDay());
            assertFalse(e.isCheckedThreeDays());
        }

        for (Event e : shouldBeCheckedOneDay) {
            assertTrue(e.isCheckedOneDay());
        }
    }
    
    @Test
    public void testWeatherAlertsCreatedForOwnerThreeDaysBefore() {
        // given an event, starting in 3 days
        Event event = createOutdoorEvent(daysFromNow(3), daysFromNow(3));
        eventManager.save(event);
        // and the weather forecast retrieved for its location
        List<FIODataPoint> forecast = new ArrayList<>();
        for (int i = 0; i < 7; i++) {
            String forecastDateString = dateFormat.format(daysFromNow(i));
            // all sunny, except the day of our event which is rain
            if (i == 3) {
                forecast.add(mockFIODataPoint(forecastDateString, "rain"));
            } else {
                forecast.add(mockFIODataPoint(forecastDateString, "clear-day"));
            }
        }

        // the weather checker should create a weather alert for our event
        cut.manageWeatherAlertsAndEmails(event, forecast);

        WeatherAlert wa = weatherAlertManager
                .findWeatherAlertsForEvent(event)
                .get(0);

        assertEquals(event, wa.getEvent());
    }

    @Test
    public void testSuggestedDateCorrect() {
        // given an event, starting in 3 days
        Event event = createOutdoorEvent(daysFromNow(3), daysFromNow(3));
        eventManager.save(event);
        // and the weather forecast retrieved for its location
        List<FIODataPoint> forecast = new ArrayList<>();
        for (int i = 0; i < 7; i++) {
            // all rainy, except five days from today 
            // (our expected proposed alternative)
            String forecastDateString = dateFormat.format(daysFromNow(i));
            if (i == 5) {
                forecast.add(mockFIODataPoint(forecastDateString, "clear-day"));
            } else {
                forecast.add(mockFIODataPoint(forecastDateString, "rain"));
            }
        }

        // the weather checker should create a weather alert for our event
        cut.manageWeatherAlertsAndEmails(event, forecast);

        WeatherAlert wa = weatherAlertManager
                .findWeatherAlertsForEvent(event)
                .get(0);

        assertEquals(event, wa.getEvent());
        assertTrue(sameDate(wa.getAlternativeDate(), daysFromNow(5)));
    }

    // utility methods
    private Date secondsFromNow(int sec) {
        Date today = new Date();
        return add(today, java.util.Calendar.SECOND, sec);
    }

    private Date daysFromNow(int days) {
        Date today = new Date();
        return add(today, java.util.Calendar.DATE, days);
    }

    private Date add(Date d, int calendarUnit, int amount) {
        Date today = new Date();
        java.util.Calendar c = java.util.Calendar.getInstance();
        c.setTime(today);
        c.add(calendarUnit, amount);
        return c.getTime();
    }

    private Calendar createCalendar() {
        Calendar c = new Calendar();
        c.setDescription("main");
        c.setPublicCalendar(false);
        c.setOwner(owner);
        return c;
    }

    private User createUser() {
        User own = new User();
        own.setEmail("test123@gmail.com");
        own.setGroupName("USER");
        own.setName("francesco");
        own.setPassword("1beb757decdc2cd8a8b6cbbb9766d23bc44069d7e8fc3c8140f05203ac3c919c");
        own.setSurname("di lorenzo");
        return own;
    }

    private Event createOutdoorEvent(Date startDate, Date endDate) {
        Event e = new Event(startDate, endDate);
        e.setTitle("AAA");
        e.setCalendar(calendar);
        e.setOutdoor(true);
        e.setLocation(new Location("a", "a", "123", "123"));
        return e;
    }

    private FIODataPoint mockFIODataPoint(String date, String icon) {
        FIODataPoint fioDataPoint = mock(FIODataPoint.class);

        when(fioDataPoint.time()).thenReturn(date);
        when(fioDataPoint.icon()).thenReturn(icon);

        return fioDataPoint;
    }

     boolean sameDate(Date d1, Date d2) {
        java.util.Calendar cal = java.util.Calendar.getInstance();

        cal.setTime(d1);
        int day1 = cal.get(java.util.Calendar.DAY_OF_MONTH);
        int month1 = cal.get(java.util.Calendar.MONTH);
        int year1 = cal.get(java.util.Calendar.YEAR);

        cal.setTime(d2);
        int day2 = cal.get(java.util.Calendar.DAY_OF_MONTH);
        int month2 = cal.get(java.util.Calendar.MONTH);
        int year2 = cal.get(java.util.Calendar.YEAR);

        return (day1 == day2 && month1 == month2 && year1 == year2);
    }
    
    // db init management
    private void clearData() throws Exception {
        utx.begin();
        em.joinTransaction();
        em.createQuery("delete from WeatherAlert").executeUpdate();
        em.createQuery("delete from Event").executeUpdate();
        em.createQuery("delete from Calendar").executeUpdate();
        em.createQuery("delete from User").executeUpdate();
        utx.commit();
        em.clear();
    }

    private void insertData() throws Exception {
        utx.begin();
        em.joinTransaction();
        owner = createUser();
        calendar = createCalendar();
        userManager.save(owner);
        calendarManager.save(calendar);
        utx.commit();
        em.clear();
    }
    
    private void startTransaction() throws Exception {
        utx.begin();
        em.joinTransaction();
    }

    @After
    public void commitTransaction() throws Exception {
        utx.commit();
    }
}
